/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    Explorer.java
 *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui.explorer;

import weka.core.Capabilities;
import weka.core.Copyright;
import weka.core.Instances;
import weka.core.Memory;
import weka.core.converters.AbstractFileLoader;
import weka.core.converters.ConverterUtils;
import weka.gui.LogPanel;
import weka.gui.Logger;
import weka.gui.LookAndFeel;
import weka.gui.WekaTaskMonitor;

import java.awt.BorderLayout;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.EventListener;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;

/**
 * The main class for the Weka explorer. Lets the user create, open, save,
 * configure, datasets, and perform ML analysis.
 * 
 * @author Len Trigg (trigg@cs.waikato.ac.nz)
 * @version $Revision: 7193 $
 */
public class Explorer extends JPanel {

	/** for serialization */
	private static final long serialVersionUID = -7674003708867909578L;

	/**
	 * Interface for classes that listen for filter changes.
	 * 
	 * @author FracPete (fracpete at waikato dot ac dot nz)
	 * @version $Revision: 7193 $
	 */
	public static interface CapabilitiesFilterChangeListener extends
			EventListener {

		/**
		 * method gets called in case of a change event
		 * 
		 * @param e
		 *            the associated change event
		 */
		public void capabilitiesFilterChanged(CapabilitiesFilterChangeEvent e);
	}

	/**
	 * This event can be fired in case the capabilities filter got changed
	 * 
	 * @author FracPete (fracpete at waikato dot ac dot nz)
	 * @version $Revision: 7193 $
	 */
	public static class CapabilitiesFilterChangeEvent extends ChangeEvent {

		/** for serialization */
		private static final long serialVersionUID = 1194260517270385559L;

		/** the capabilities filter */
		protected Capabilities m_Filter;

		/**
		 * Constructs a GOECapabilitiesFilterChangeEvent object.
		 * 
		 * @param source
		 *            the Object that is the source of the event
		 * @param filter
		 *            the responsible capabilities filter
		 */
		public CapabilitiesFilterChangeEvent(Object source, Capabilities filter) {
			super(source);
			m_Filter = filter;
		}

		/**
		 * returns the associated Capabilities filter
		 * 
		 * @return the filter
		 */
		public Capabilities getFilter() {
			return m_Filter;
		}
	}

	/**
	 * A common interface for panels to be displayed in the Explorer
	 * 
	 * @author FracPete (fracpete at waikato dot ac dot nz)
	 * @version $Revision: 7193 $
	 */
	public static interface ExplorerPanel {

		/**
		 * Sets the Explorer to use as parent frame (used for sending
		 * notifications about changes in the data)
		 * 
		 * @param parent
		 *            the parent frame
		 */
		public void setExplorer(Explorer parent);

		/**
		 * returns the parent Explorer frame
		 * 
		 * @return the parent
		 */
		public Explorer getExplorer();

		/**
		 * Tells the panel to use a new set of instances.
		 * 
		 * @param inst
		 *            a set of Instances
		 */
		public void setInstances(Instances inst);

		/**
		 * Returns the title for the tab in the Explorer
		 * 
		 * @return the title of this tab
		 */
		public String getTabTitle();

		/**
		 * Returns the tooltip for the tab in the Explorer
		 * 
		 * @return the tooltip of this tab
		 */
		public String getTabTitleToolTip();
	}

	/**
	 * A common interface for panels in the explorer that can handle logs
	 * 
	 * @author FracPete (fracpete at waikato dot ac dot nz)
	 * @version $Revision: 7193 $
	 */
	public static interface LogHandler {

		/**
		 * Sets the Logger to receive informational messages
		 * 
		 * @param newLog
		 *            the Logger that will now get info messages
		 */
		public void setLog(Logger newLog);
	}

	/** The panel for preprocessing instances */
	protected PreprocessPanel m_PreprocessPanel = new PreprocessPanel();

	/** Contains all the additional panels apart from the pre-processing panel */
	protected Vector<ExplorerPanel> m_Panels = new Vector<ExplorerPanel>();

	/** The tabbed pane that controls which sub-pane we are working with */
	protected JTabbedPane m_TabbedPane = new JTabbedPane();

	/** The panel for log and status messages */
	protected LogPanel m_LogPanel = new LogPanel(new WekaTaskMonitor());

	/** the listeners that listen to filter changes */
	protected HashSet<CapabilitiesFilterChangeListener> m_CapabilitiesFilterChangeListeners = new HashSet<CapabilitiesFilterChangeListener>();

	/**
	 * Creates the experiment environment gui with no initial experiment
	 */
	public Explorer() {

		String date = (new SimpleDateFormat("EEEE, d MMMM yyyy"))
				.format(new Date());
		m_LogPanel.logMessage(Messages.getInstance().getString(
				"Explorer_LogPanel_LogMessage_Text_First"));
		m_LogPanel.logMessage(Messages.getInstance().getString(
				"Explorer_LogPanel_LogMessage_Text_Second")
				+ Copyright.getFromYear()
				+ Messages.getInstance().getString(
						"Explorer_LogPanel_LogMessage_Text_Third")
				+ Copyright.getToYear()
				+ " "
				+ Copyright.getOwner()
				+ ", "
				+ Copyright.getAddress());
		m_LogPanel.logMessage(Messages.getInstance().getString(
				"Explorer_LogPanel_LogMessage_Text_Fourth")
				+ Copyright.getURL());
		m_LogPanel.logMessage(Messages.getInstance().getString(
				"Explorer_LogPanel_LogMessage_Text_Fifth")
				+ date);
		m_LogPanel.statusMessage(Messages.getInstance().getString(
				"Explorer_LogPanel_StatusMessage_Text_First"));

		// intialize pre-processpanel
		m_PreprocessPanel.setLog(m_LogPanel);
		m_TabbedPane.addTab(m_PreprocessPanel.getTabTitle(), null,
				m_PreprocessPanel, m_PreprocessPanel.getTabTitleToolTip());

		// initialize additional panels
		String[] tabs = ExplorerDefaults.getTabs();
		Hashtable<String, HashSet> tabOptions = new Hashtable<String, HashSet>();
		for (int i = 0; i < tabs.length; i++) {
			try {
				// determine classname and additional options
				String[] optionsStr = tabs[i].split(":");
				String classname = optionsStr[0];
				HashSet options = new HashSet();
				tabOptions.put(classname, options);
				for (int n = 1; n < optionsStr.length; n++)
					options.add(optionsStr[n]);

				// setup panel
				ExplorerPanel panel = (ExplorerPanel) Class.forName(classname)
						.newInstance();
				panel.setExplorer(this);
				m_Panels.add(panel);
				if (panel instanceof LogHandler)
					((LogHandler) panel).setLog(m_LogPanel);
				m_TabbedPane.addTab(panel.getTabTitle(), null, (JPanel) panel,
						panel.getTabTitleToolTip());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		// setup tabbed pane
		m_TabbedPane.setSelectedIndex(0);
		for (int i = 0; i < m_Panels.size(); i++) {
			HashSet options = tabOptions.get(m_Panels.get(i).getClass()
					.getName());
			m_TabbedPane.setEnabledAt(i + 1, options.contains("standalone"));
		}

		// setup notification for dataset changes
		m_PreprocessPanel
				.addPropertyChangeListener(new PropertyChangeListener() {
					public void propertyChange(PropertyChangeEvent e) {
						for (int i = 0; i < m_Panels.size(); i++) {
							m_Panels.get(i).setInstances(
									m_PreprocessPanel.getInstances());
							m_TabbedPane.setEnabledAt(i + 1, true);
						}
					}
				});

		// add listeners for changes in the capabilities
		m_PreprocessPanel.setExplorer(this);
		addCapabilitiesFilterListener(m_PreprocessPanel);
		for (int i = 0; i < m_Panels.size(); i++) {
			if (m_Panels.get(i) instanceof CapabilitiesFilterChangeListener)
				addCapabilitiesFilterListener((CapabilitiesFilterChangeListener) m_Panels
						.get(i));
		}

		// add components to layout
		setLayout(new BorderLayout());
		add(m_TabbedPane, BorderLayout.CENTER);
		add(m_LogPanel, BorderLayout.SOUTH);
	}

	/**
	 * returns all the panels, apart from the PreprocessPanel
	 * 
	 * @return the currently displayed panels w/o PreprocessPanel
	 */
	public Vector<ExplorerPanel> getPanels() {
		return m_Panels;
	}

	/**
	 * returns the instance of the PreprocessPanel being used in this instance
	 * of the Explorer
	 * 
	 * @return the panel
	 */
	public PreprocessPanel getPreprocessPanel() {
		return m_PreprocessPanel;
	}

	/**
	 * returns the tabbed pane of the Explorer
	 * 
	 * @return the tabbed pane
	 */
	public JTabbedPane getTabbedPane() {
		return m_TabbedPane;
	}

	/**
	 * adds the listener to the list of objects that listen for changes of the
	 * CapabilitiesFilter
	 * 
	 * @param l
	 *            the listener to add
	 * @see #m_CapabilitiesFilterChangeListeners
	 */
	public void addCapabilitiesFilterListener(CapabilitiesFilterChangeListener l) {
		m_CapabilitiesFilterChangeListeners.add(l);
	}

	/**
	 * Removes the specified listener from the set of listeners if it is
	 * present.
	 * 
	 * @param l
	 *            the listener to remove
	 * @return true if the listener was registered
	 */
	public boolean removeCapabilitiesFilterListener(
			CapabilitiesFilterChangeListener l) {
		return m_CapabilitiesFilterChangeListeners.remove(l);
	}

	/**
	 * notifies all the listeners of a change
	 * 
	 * @param filter
	 *            the affected filter
	 */
	public void notifyCapabilitiesFilterListener(Capabilities filter) {
		for (CapabilitiesFilterChangeListener l : m_CapabilitiesFilterChangeListeners) {
			if (l == this)
				continue;
			l.capabilitiesFilterChanged(new CapabilitiesFilterChangeEvent(this,
					filter));
		}
	}

	/**
	 * variable for the Explorer class which would be set to null by the memory
	 * monitoring thread to free up some memory if we running out of memory
	 */
	private static Explorer m_explorer;

	/** for monitoring the Memory consumption */
	private static Memory m_Memory = new Memory(true);

	/**
	 * Tests out the explorer environment.
	 * 
	 * @param args
	 *            ignored.
	 */
	public static void main(String[] args) {

		weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO,
				Messages.getInstance().getString("Explorer_Main_Logger_Text"));

		LookAndFeel.setLookAndFeel();

		try {
			// uncomment to disable the memory management:
			// m_Memory.setEnabled(false);

			m_explorer = new Explorer();
			final JFrame jf = new JFrame(Messages.getInstance().getString(
					"Explorer_Main_JFrame_Text"));
			jf.getContentPane().setLayout(new BorderLayout());
			jf.getContentPane().add(m_explorer, BorderLayout.CENTER);
			jf.addWindowListener(new WindowAdapter() {
				public void windowClosing(WindowEvent e) {
					jf.dispose();
					System.exit(0);
				}
			});
			jf.pack();
			jf.setSize(800, 600);
			jf.setVisible(true);
			Image icon = Toolkit.getDefaultToolkit().getImage(
					m_explorer.getClass().getClassLoader()
							.getResource("weka/gui/weka_icon_new_48.png"));
			jf.setIconImage(icon);

			if (args.length == 1) {
				System.err.println(Messages.getInstance().getString(
						"Explorer_Main_Run_Error_Text")
						+ args[0]);
				AbstractFileLoader loader = ConverterUtils
						.getLoaderForFile(args[0]);
				loader.setFile(new File(args[0]));
				m_explorer.m_PreprocessPanel.setInstancesFromFile(loader);
			}

			Thread memMonitor = new Thread() {
				public void run() {
					while (true) {
						try {
							// System.out.println("Before sleeping.");
							this.sleep(4000);

							System.gc();

							if (m_Memory.isOutOfMemory()) {
								// clean up
								jf.dispose();
								m_explorer = null;
								System.gc();

								// stop threads
								m_Memory.stopThreads();

								// display error
								System.err
										.println(Messages
												.getInstance()
												.getString(
														"Explorer_Main_Run_Error_Text_First"));
								m_Memory.showOutOfMemory();
								System.err
										.println(Messages
												.getInstance()
												.getString(
														"Explorer_Main_Run_Error_Text_Second"));
								System.exit(-1);
							}

						} catch (InterruptedException ex) {
							ex.printStackTrace();
						}
					}
				}
			};

			memMonitor.setPriority(Thread.MAX_PRIORITY);
			memMonitor.start();
		} catch (Exception ex) {
			ex.printStackTrace();
			System.err.println(ex.getMessage());
		}
	}
}
